package menus.common;

import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Vector;

import fr.lri.swingstates.gestures.Gesture;

/**
 * Gesture analyzer based on an ad-hoc algorithm and a smoothing principle
 * 
 * @author mathieu nancel
 *
 */
public class AdHocGestureAnalyzer extends AbstractGestureAnalyzer {

	// ------------------------------------------------------------------
	// 														Constants																								
	// ------------------------------------------------------------------
	
	/**
	 * Default ad-hoc margin, above which a point is considered an angle from a segment
	 */
	protected static final int ADHOC_MARGIN = 15;

	/**
	 * Default smoothing margin, above which a point is considered an angle from a segment
	 */
	protected static final int SMOOTHING_MARGIN = 15;
	
	/**
	 * Default smoothing angle, above which a point is removed from the final gesture/mark
	 */
	protected static final double SMOOTHING_ANGLE = 3 * Math.PI / 4;
	
	protected static final double[] considerationInterval = {.1, .9};
	
	
	// ------------------------------------------------------------------
	// 												Protected variables																								
	// ------------------------------------------------------------------

	/**
	 * The gesture point list
	 */  
	
	protected Vector<Point2D> gesturePoints;

	// ------------------------------------------------------------------

	/**
	 * Line used to compute the distance between a point and a segment
	 */
	protected Line2D line;


	// ------------------------------------------------------------------
	// 													Methods																								
	// ------------------------------------------------------------------

	/**
	 * First algorithm used for the gesture analyze<br/>
	 * <ul>
	 * <li>Consider the Gesture as a segment going from start point to end point</li>
	 * <li>While there's no more points distant from a certain distance from the segments
	 * 		<ul>
	 * 				<li>Pick the farthest point from all segments and split this segment into two segments</li>
	 * 		</ul>
	 * </li> 
	 * <li>Apply a simple smoothing algorithm in case the first point picked wasn't relevant</li>
	 * </ul>
	 * 
	 * @param g The gesture about to be analyzed
	 * @return The smoothed shape of the gesture
	 */
	public Vector<Point2D> adHocAnalyse(Gesture g){
		
		// List of the recognized points of the gesture after the analyze
		
		Vector<Point2D> filteredPoints;

		// List of the recognized angles indexes in the gesture. Allows to simply browse the gesture points inside an interval
		
		ArrayList<Integer> indexes;

		
		
		// Farthest point, for each segment

		Point2D farPoint;

		// Indicates if a point has been found far enough from the current segment

		boolean stop = false;

		
		// Initializations
		
		extractDataFromGesture(g);
		
		filteredPoints = new Vector<Point2D>();
		indexes = new ArrayList<Integer>();
		gesturePoints = g.getPoints();
		
		if (gesturePoints.size() > 2){
		
			/* Consider the Gesture as a segment going from start point to end point */
			
			int firstIndex = getClosestPointIndex(g, considerationInterval[0]);
			int lastIndex = getClosestPointIndex(g, considerationInterval[1]);
			
			filteredPoints.add(gesturePoints.get(firstIndex));
			filteredPoints.add(gesturePoints.get(lastIndex));
			
			indexes.add(firstIndex);
			indexes.add(lastIndex);
			
			while (!stop) {
	
				stop = true;
				
				/* While there's no more points distant from a certain distance from the segments */
	
				for (int i = 0; i < filteredPoints.size()-1; i++) {
	
					line = new Line2D.Double(filteredPoints.get(i), filteredPoints.get(i+1));
	
					/* Pick the farthest point from all segments and split this segment into two segments */
					
					farPoint = getFarthestPoint(indexes.get(i), indexes.get(i+1));
	
					if (farPoint != null && line.ptLineDist(farPoint) > ADHOC_MARGIN){
	
						indexes.add(i+1, gesturePoints.indexOf(farPoint));
						filteredPoints.add(i+1, farPoint);
	
						stop = false;
					}
				}
			}
	
			return smooth(filteredPoints);
		}
		
		return gesturePoints;
	}


	/**
	 * Second algorithm, used to smooth the simplified mark obtained. It firsts verifies that all the points
	 * verify the distance condition (an other distance can be specified for this one). Then it verifies that 
	 * the angles corresponding to the mark points are "interesting", i.e. are sharp enough to represent
	 * a direction change.  
	 * 
	 * @param simplifiedMark The mark coming from the first algrithm
	 * @return The smoothed path
	 */
	public Vector<Point2D> smooth(Vector<Point2D> simplifiedMark){

		// Point list corresponding to the path found by the first algorithm. "Uninteresting" points will be removed from it
		
		Vector<Point2D> smoothedPoints = simplifiedMark;

		// Indicates if a point has been found far enough from the current segment
		
		boolean ok = false;
		
		// The two first ones are used to compute the third one
		
		double angle1, angle2, angle;

		// 		First smoothing : distances
		// Verification is made for each angle, i.e. for every points except the first and the last

		for (int i = 1; i < smoothedPoints.size()-1; i++) {

			ok = false;

			while (!ok && i+1 != smoothedPoints.size()){

				line = new Line2D.Double(smoothedPoints.get(i-1), smoothedPoints.get(i+1));

				if (line.ptLineDist(smoothedPoints.get(i)) < SMOOTHING_MARGIN){

					smoothedPoints.remove(i);

				}

				else{

					ok = true;

				}

			}
		}

		
		//		Second smoothing : angles
		// Verification is made for each angle, i.e. for every points except the first and the last

		for (int j = 1; j < smoothedPoints.size()-1; j++){

			angle1 = Math.atan2(smoothedPoints.get(j).getY() - smoothedPoints.get(j-1).getY(), smoothedPoints.get(j).getX() - smoothedPoints.get(j-1).getX());
			angle2 = Math.atan2(smoothedPoints.get(j+1).getY() - smoothedPoints.get(j).getY(), smoothedPoints.get(j+1).getX() - smoothedPoints.get(j).getX());
			
			// angle is computed to be in range [-PI, PI[
			
			angle = ( -angle1 + Math.PI + (angle2 + Math.PI) ) % (2 * Math.PI) - Math.PI;
			
			if (Math.abs(angle) > SMOOTHING_ANGLE) {
				smoothedPoints.remove(j);
				
				// This way we avoid forgetting angles
				j--;
			}

		}

		return smoothedPoints;
	}


	/**
	 * Finds the farthest gesturePoints point from the segment going from the <i>from'th</i> gesturePoints element to the <i>to'th</i>.
	 * 
	 * @param from The segment starting point index
	 * @param to The segment ending point index
	 * @return	The farthest point from the specified segment, or the last one if there's no points between the two specified
	 */
	protected Point2D getFarthestPoint(int from, int to){
		
		double bestDistance = 0;

		int bestIndex = from+1;

		for (int i = bestIndex; i < to; i++){
	
			if (bestDistance < line.ptLineDist(gesturePoints.elementAt(i))) {

				bestDistance = line.ptLineDist(gesturePoints.elementAt(i));
				bestIndex = i;

			}
		}
		return gesturePoints.get(bestIndex);

	}

}
